﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace RoofRunner.GameComponents.Rendering
{
    public class LODSystem
    {
        #region Properties
        private List<ILODObject> LODObjects { get; set; }
        #endregion // Properties


        #region Constructors
        public LODSystem()
        {
            this.LODObjects = new List<ILODObject>();
        }
        #endregion // Constructors


        #region Methods
        public void Update(GameTime gameTime, RenderWorld rw)
        {
            var campos = rw.Camera.Position;
            foreach (var x in this.LODObjects)
            {
                var dis = x.GetDistance(ref campos);
                if (dis < 0) dis *= -1; // da Kameradrehung bei GetDistance nicht berücksichtigt wird
                x.LODDefinition.CheckLOD(dis, x);
            }
        }


        public void AddLODObject(ILODObject obj)
        {
            this.LODObjects.Add(obj);
        }


        public void RemoveLODObject(ILODObject obj)
        {
            this.LODObjects.Remove(obj);
        }
        #endregion // Methods


        #region Methods
        #endregion // Methods
    }



    public interface ILODObject
    {
        #region Properties
        LODDefinition LODDefinition { get; }
        #endregion // Properties


        #region Methods
        float GetDistance(ref Vector3 cameraPosition);
        void LODUpdate(LODLevel level);
        #endregion // Methods
    }



    public class LODLevel
    {
        #region Properties
        public Model Model { get; private set; }
        public float Distance { get; private set; }
        #endregion // Properties


        #region Constructors
        public LODLevel(float distance, Model m)
        {
            this.Model = m;
            this.Distance = distance;
        }
        #endregion // Constructors
    }

    public class LODDefinition
    {
        #region Fields
        private float _keyOfCurrentLOD = 0;
        #endregion // Fields


        #region Properties
        private SortedList<float, LODLevel> LODLevels { get; set; }
        #endregion // Properties


        #region Constructors
        public LODDefinition() { this.LODLevels = new SortedList<float, LODLevel>(); }
        #endregion // Constructors


        #region Methods
        



        public void Add(float distance, LODLevel level)
        {
            if(this.LODLevels.Count == 0) distance = 0;
            this.LODLevels.Add(distance, level);
        }
        public void Add(float distance, Model m)
        {
            if (m != null && distance >= 0)
                this.Add(distance, new LODLevel(distance, m));
        }

        public void Remove(LODLevel level)
        {
            int index = this.LODLevels.IndexOfValue(level);
            if (index >= 0)
                this.LODLevels.RemoveAt(index);
        }


        internal void CheckLOD(float distance, ILODObject callingObject)
        {
            if (distance == _keyOfCurrentLOD) return; // No Update needed

            int indexOfCurrent = this.LODLevels.IndexOfKey(_keyOfCurrentLOD);
            
            if (indexOfCurrent == 0 && distance < _keyOfCurrentLOD) return; // Nearest LOD already loaded
            if (indexOfCurrent == this.LODLevels.Count - 1 && distance > _keyOfCurrentLOD) return; // farest LOD already loaded

            _keyOfCurrentLOD = this.LODLevels.Keys[GetIndexOfCurrentLOD(distance, this.LODLevels.Keys, indexOfCurrent)];
            callingObject.LODUpdate(this.LODLevels[_keyOfCurrentLOD]);
        }


        private int GetIndexOfCurrentLOD(float distance, IList<float> distances, int current)
        {
            if (current < 0) return 0;
            if (current >= distances.Count) return distances.Count - 1;

            if (distance < distances[current])
                return GetIndexOfCurrentLOD(distance, distances, current - 1);
            else
            {
                if (current + 1 < distances.Count && distance < distances[current + 1])
                    return current;
            }
            return GetIndexOfCurrentLOD(distance, distances, current + 1);
        }
        #endregion // Methods
    }
}
